﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Accountant.Controls;
using Accountant.DataModel;
using Accountant.Primitives;
using Accountant.Properties;
using Accountant.Utils;
using Caliburn.Micro;

namespace Accountant.ViewModels
{
  public abstract class OperationsViewModel : PropertyChangedBase
  {
    public OperationsSource Source { get; set; }
    public abstract string Caption { get; }

    public void Activate(object operation)
    {
      bool? result;
      var windowManager = IoC.Get<IWindowManager>();
      var transaction = operation as Transaction;
      if (transaction != null)
      {
        var clone = Clone(transaction);
//        if (transaction.Debit.Root().IsSpecial(KnownAccounts.Starting))
//        {
//          result = windowManager.ShowDialog(new AddOperationViewModel(
//            Source.Data, clone, TransactionDirection.Starting));
//        }
        if (transaction.IsExpense())
        {
          result = windowManager.ShowDialog(new AddOperationViewModel(
            Source.Data, clone, TransactionDirection.Expense));
        }
        else if (transaction.IsIncome())
        {
          result = windowManager.ShowDialog(new AddOperationViewModel(
            Source.Data, clone, TransactionDirection.Income));
        }
        else if (transaction.IsTransfer())
        {
          result = windowManager.ShowDialog(new AddOperationViewModel(
            Source.Data, clone, TransactionDirection.Transfer));
        }
        else
        {
          result = windowManager.ShowDialog(new AddOperationViewModel(
            Source.Data, clone, TransactionDirection.Starting));
        }
        if (result == true)
        {
          Copy(transaction, clone);
          Source.Data.SaveChanges();
        }
      }
      var exchange = operation as ExchangeTransaction;
      if (exchange != null)
      {
        var clone = Clone(exchange);
        result = windowManager.ShowDialog(new AddExchangeViewModel(Source.Data, clone));
        if (result == true)
        {
          exchange.Transactions.Merge(clone.Transactions, Copy);
          Source.Data.SaveChanges();
        }
      }

    }

    private static ExchangeTransaction Clone(ExchangeTransaction source)
    {
      return new ExchangeTransaction(source.Transactions.Select(Clone).ToList());
    }
    private static Transaction Clone(Transaction source)
    {
      var dest = new Transaction();
      Copy(dest, source);
      return dest;
    }
    private static void Copy(Transaction dest, Transaction source)
    {
      dest.Amount = source.Amount;
      dest.Comment = source.Comment;
      dest.Credit = source.Credit;
      dest.Debit = source.Debit;
      dest.Timestamp = source.Timestamp;
      dest.Currency = source.Currency;
    }

    protected OperationsViewModel(OperationsSource data)
    {
      Source = data;

      Source.PropertyChanged +=
        (s, e) =>
        {
          UpdateCache();

          NotifyOfPropertyChange(() => Caption);
          NotifyOfPropertyChange(() => Prev);
          NotifyOfPropertyChange(() => Curr);
          NotifyOfPropertyChange(() => Balance);
          NotifyOfPropertyChange(() => BalanceVisibility);
        };

      UpdateCache();
    }

    private void UpdateCache()
    {
      Prev = Source.Data.Transactions
        .Where(t => t.Timestamp < Source.Start
                    && IsRelevant(t))
        .OrderBy(t => t.Timestamp).ToList();

      Curr = Source.Data.Transactions
        .Where(t => t.Timestamp >= Source.Start
                    && t.Timestamp <= Source.Stop
                    && IsRelevant(t))
        .OrderBy(t => t.Timestamp).ToList();
    }

    public IEnumerable<Transaction> Prev { get; private set; }
    public IEnumerable<Transaction> Curr { get; private set; }
    public IEnumerable<GroupTransaction> CurrWithGrouping
    {
      get { return Curr.GroupTransactions().Where(IsRelevant); }
    }

    public IEnumerable<BalanceRow> Balance
    {
      get
      {
        Func<Account, bool> relevantSide =
          a => Source.IncludeDerivedAccounts
                 ? a.Is(Source.Account)
                 : a == Source.Account;

        var incomingBalance = Prev.Balance(relevantSide);
        var outgoingBalance = Curr.Balance(relevantSide);
        var k = InvertBalance ? -1m : 1m;
        var res = new[] {incomingBalance, outgoingBalance}
          .SelectMany(bag => bag)
          .Select(m => m.Currency)
          .Distinct()
          .Select(curr => new BalanceRow
                            {
                              Currency = curr,
                              Prev = k*incomingBalance[curr],
                              Curr = k*outgoingBalance[curr],
                              Total = k*(incomingBalance[curr] + outgoingBalance[curr])
                            }).ToList();
        var baseCurrency = Source.Data.Currencies.
          Single(c => c.Name == Settings.Default.BaseCurrency);
        var sum = res.Aggregate(new decimal?(0), (current, r) => 
          current + r.Total.Convert(r.Currency, baseCurrency, DateTime.Now));
        if (sum != null)
          res.Add(new BalanceRow
                    {
                      Currency = new Currency { Name = "Total:"},
                      Prev = (decimal) res.Sum(r => r.Prev.Convert(r.Currency, baseCurrency, DateTime.Now)),
                      Curr = (decimal) res.Sum(r => r.Curr.Convert(r.Currency, baseCurrency, DateTime.Now)),
                      Total = (decimal) res.Sum(r => r.Total.Convert(r.Currency, baseCurrency, DateTime.Now))
                    });
        return res;
      }
    }

    public Visibility BalanceVisibility
    {
      get { return Balance.Any() ? Visibility.Visible : Visibility.Collapsed; }
    }

    #region ' ProposedDateFormat '

    private string _proposedDateFormat;

    public string ProposedDateFormat
    {
      get { return _proposedDateFormat; }
      set
      {
        if (value == _proposedDateFormat) return;
        _proposedDateFormat = value;
        NotifyOfPropertyChange(() => ProposedDateFormat);
      }
    }

    #endregion

    protected virtual bool IsRelevant(GroupTransaction t)
    {
      return true;
    }
    protected virtual bool IsRelevant(Transaction t)
    {
      if (Source.IncludeDerivedAccounts) return t.Is(Source.Account);
      return t.Credit == Source.Account || t.Debit == Source.Account;
    }
    protected virtual bool InvertBalance { get { return false; } }

    public void DeclareCorrect()
    {
      if (MessageBox.Show("Sure?", "Q", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
      {
        Source.Account.LastRevisionDate = DateTime.UtcNow;
        Source.Data.SaveChanges();
      }

    }
  }

  public sealed class GenericOperationsViewModel : OperationsViewModel
  {
    public override string Caption { get { return "Общий (" + Curr.Count() + ")"; } }

    public GenericOperationsViewModel(OperationsSource data)
      : base(data)
    {
    }
  }
  public sealed class ExpenseOperationsViewModel : OperationsViewModel
  {
    public override string Caption { get { return "Расходы (" + Curr.Count() + ")"; } }

    public ExpenseOperationsViewModel(OperationsSource data)
      : base(data)
    {
    }

    protected override bool IsRelevant(Transaction t)
    {
      return base.IsRelevant(t) && t.IsExpense();
    }
    protected override bool InvertBalance { get { return true; } }
  }

  public sealed class ExchangeOperationsViewModel : OperationsViewModel
  {
    public IEnumerable<ExchangeTransaction> Exchanges
    {
      get { return CurrWithGrouping.OfType<ExchangeTransaction>(); }
    }

    public override string Caption { get { return "Обмен (" + Exchanges.Count() + ")"; } }

    public ExchangeOperationsViewModel(OperationsSource data)
      : base(data)
    {
    }

    protected override bool IsRelevant(Transaction t)
    {
      return base.IsRelevant(t) && t.IsExchange();
    }
  }

  public sealed class IncomeOperationsViewModel : OperationsViewModel
  {
    public override string Caption { get { return "Доходы (" + Curr.Count() + ")"; } }

    public IncomeOperationsViewModel(OperationsSource data)
      : base(data)
    {
    }

    protected override bool IsRelevant(Transaction t)
    {
      return base.IsRelevant(t) && t.IsIncome();
    }
  }

  public sealed class TransferOperationsViewModel : OperationsViewModel
  {
    public override string Caption { get { return "Переводы (" + Curr.Count() + ")"; } }

    public TransferOperationsViewModel(OperationsSource data)
      : base(data)
    {
    }

    protected override bool IsRelevant(Transaction t)
    {
      return base.IsRelevant(t) && t.IsTransfer();
    }
  }

  public sealed class TakeCreditOperationsViewModel : OperationsViewModel
  {
    public override string Caption { get { return "Взято в долг (" + Curr.Count() + ")"; } }

    public TakeCreditOperationsViewModel(OperationsSource data)
      : base(data)
    {
    }

    protected override bool IsRelevant(Transaction t)
    {
      return base.IsRelevant(t) && t.IsLiability();
    }
  }
}